package com.onesports.intelligent.k12.polarlight.service.student;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.onesports.framework.kit.common.util.ParamUtils;
import com.onesports.framework.kit.common.util.TimeUtils;
import com.onesports.intelligent.k12.polarlight.common.domain.entity.BaseEntity;
import com.onesports.intelligent.k12.polarlight.common.errors.exceptions.BusinessException;
import com.onesports.intelligent.k12.polarlight.common.utils.DateUtils;
import com.onesports.intelligent.k12.polarlight.domain.dto.attendance.AttendanceDTO;
import com.onesports.intelligent.k12.polarlight.domain.dto.student.AttendanceQueryDTO;
import com.onesports.intelligent.k12.polarlight.domain.entity.course.*;
import com.onesports.intelligent.k12.polarlight.domain.entity.organization.CampusStaffRel;
import com.onesports.intelligent.k12.polarlight.domain.entity.organization.OrganizationCampus;
import com.onesports.intelligent.k12.polarlight.domain.entity.organization.OrganizationStaff;
import com.onesports.intelligent.k12.polarlight.domain.entity.student.StudentAttendance;
import com.onesports.intelligent.k12.polarlight.domain.entity.sys.Account;
import com.onesports.intelligent.k12.polarlight.domain.vo.attendance.ScheduleDetailVO;
import com.onesports.intelligent.k12.polarlight.domain.vo.student.ClassRecordVO;
import com.onesports.intelligent.k12.polarlight.domain.vo.student.StudentAttendanceVO;
import com.onesports.intelligent.k12.polarlight.enums.AttendanceStatusEnum;
import com.onesports.intelligent.k12.polarlight.enums.ClassInfoStatusEnum;
import com.onesports.intelligent.k12.polarlight.enums.ScheduleDetailStatusEnum;
import com.onesports.intelligent.k12.polarlight.mapper.course.ClassInfoMapper;
import com.onesports.intelligent.k12.polarlight.mapper.course.CourseScheduleDetailMapper;
import com.onesports.intelligent.k12.polarlight.mapper.course.SpellingClassPeriodMapper;
import com.onesports.intelligent.k12.polarlight.mapper.organization.CampusStaffRelMapper;
import com.onesports.intelligent.k12.polarlight.mapper.organization.OrganizationCampusMapper;
import com.onesports.intelligent.k12.polarlight.mapper.student.StudentAttendanceMapper;
import com.onesports.intelligent.k12.polarlight.security.SecurityUtils;
import com.onesports.intelligent.k12.polarlight.service.campus.OrganizationCampusService;
import com.onesports.intelligent.k12.polarlight.service.common.CommonService;
import com.onesports.intelligent.k12.polarlight.service.course.ClassStudentScheduleDetailService;
import com.onesports.intelligent.k12.polarlight.service.course.SpellingClassPeriodService;
import com.onesports.intelligent.k12.polarlight.service.order.OrderService;
import com.onesports.intelligent.k12.polarlight.service.organization.OrganizationStaffService;
import com.onesports.intelligent.k12.polarlight.service.sys.AccountService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Array;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 考勤服务
 *
 * @author clw
 * @date 2022-07-15 16:34:32
 */
@Service
@RequiredArgsConstructor
public class StudentAttendanceService extends ServiceImpl<StudentAttendanceMapper, StudentAttendance> {


    private final StudentAttendanceMapper studentAttendanceMapper;
    private final ClassStudentService classStudentService;
    private final ClassInfoMapper classInfoMapper;
    private final CourseScheduleDetailMapper scheduleDetailMapper;
    private final CommonService commonService;
    private final SpellingClassPeriodService spellingClassPeriodService;
    private final ClassStudentScheduleDetailService classStudentScheduleDetailService;
    private final OrderService orderService;


    public List<StudentAttendanceVO> stuAttendanceInfo(List<String> detailIds, List<String> classStudentIds) {
        return this.studentAttendanceMapper.stuAttendanceInfo(detailIds, classStudentIds);
    }

    /**
     * 分页
     *
     * @param dto  入参
     * @param page 分页参数
     * @return IPage<ScheduleDetailVO>
     */
    public IPage<ScheduleDetailVO> attendancePage(Page<ScheduleDetailVO> page, AttendanceQueryDTO dto) {
        IPage<ScheduleDetailVO> iPage = this.studentAttendanceMapper.attendancePage(page, dto);
        List<ScheduleDetailVO> records = iPage.getRecords();
        if (ParamUtils.isNotEmpty(records)) {
            //获取校区
            Map<String, String> campusMap = this.commonService.campusMap();
            //获取课程Map
            Map<String, String> courseMap = this.commonService.courseMap();
            //获取教练map
            Map<String, String> coachMap = this.commonService.coachMap();
            records.forEach(i -> {
                i.setCampusName(campusMap.get(i.getCampusId()));
                i.setCourseName(courseMap.get(i.getCourseId()));
                i.setCoachName(coachMap.get(i.getCoachId()));
            });
        }
        return iPage;
    }


    /**
     * 考勤学员详情
     *
     * @param scheduleDetailId 课表详情id
     * @param page             分页参数
     * @return List<StudentAttendanceVO>
     */
    public IPage<StudentAttendanceVO> detail(Page<StudentAttendanceVO> page, String scheduleDetailId, String classId) {
        return this.studentAttendanceMapper.detail(page, scheduleDetailId, classId);
    }

    /**
     * 上课记录
     *
     * @param studentId 学员id
     * @param campusIds 校区ids 学员管理那边调用的时候才要传
     * @return List<ClassRecordVO>
     */
    public List<ClassRecordVO> classRecordList(String studentId, List<String> campusIds) {
        //获取班级学员
        List<ClassStudent> list = this.classStudentService.list(new LambdaQueryWrapper<ClassStudent>()
                .eq(ClassStudent::getStudentId, studentId));

        if (ParamUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        List<String> classStudentIds = list.stream().map(BaseEntity::getId).collect(Collectors.toList());
        List<ClassRecordVO> records = this.studentAttendanceMapper.classRecordList(classStudentIds, campusIds, Arrays.asList(ClassInfoStatusEnum.IN_CLASS.getValue(), ClassInfoStatusEnum.FAIL_CLASS.getValue()));
        if (ParamUtils.isNotEmpty(records)) {
            //班级
            Map<String, String> classMap = this.commonService.classMap();
            //课程
            Map<String, String> courseMap = this.commonService.courseMap();
            //校区
            Map<String, String> campusMap = this.commonService.campusMap();
            //教练
            Map<String, String> coachMap = this.commonService.coachMap();
            records.forEach(i -> {
                i.setClassName(classMap.get(i.getClassId()));
                i.setCourseName(courseMap.get(i.getCourseId()));
                i.setCampusName(campusMap.get(i.getCampusId()));
                i.setCoachName(coachMap.get(i.getCoachId()));
            });
        }
        return records;
    }


    /**
     * 学员考勤
     *
     * @param dto 入参
     */
    @Transactional(rollbackFor = Exception.class)
    public void attendance(AttendanceDTO dto) {
        Date date = new Date();
        //校验班级状态
        ClassInfo classInfo = this.checkClassInfo(dto.getClassId());
        //校验上课时间
        CourseScheduleDetail scheduleDetail = this.checkClassTime(dto.getId(), date);
        if (dto.getAttendanceStatus().equals(AttendanceStatusEnum.REGISTERED.getValue())) {
            //更改班级状态为开班中
            classInfo.setClassStatus(ClassInfoStatusEnum.OPEN_CLASS.getValue());
            this.classInfoMapper.updateById(classInfo);
            scheduleDetail.setStatus(ScheduleDetailStatusEnum.ATTENDANCE_ING.getValue());
        }
        //获取班级学员
        List<ClassStudent> classStudents = this.classStudentService
                .list(new LambdaQueryWrapper<ClassStudent>()
                        .eq(ClassStudent::getClassInfoId, dto.getClassId())
                        .eq(ClassStudent::getActive, true));
        if (ParamUtils.isEmpty(classStudents)) {
            throw new BusinessException("该班级下没有可考勤的学员");
        }
        List<String> classStudentIds = classStudents.stream().map(BaseEntity::getId).collect(Collectors.toList());
        //获取班级学员课表
        List<ClassStudentScheduleDetail> stuScheduleDetails = this.classStudentScheduleDetailService.listStuScheduleDetail(classStudentIds, scheduleDetail.getId());
        if (ParamUtils.isEmpty(stuScheduleDetails)) {
            throw new BusinessException("当前课表没有可考勤学员");
        }

        Map<String, ClassStudentScheduleDetail> studentScheduleDetailMap = stuScheduleDetails.stream().collect(Collectors.toMap(ClassStudentScheduleDetail::getClassStudentId, stu -> stu));

        String attendanceStatus = dto.getAttendanceStatus();
        //更改考勤状态
        List<StudentAttendance> studentAttendances = this.optStudentAttendance(dto, date, scheduleDetail, attendanceStatus);
        List<StudentAttendance> signData = studentAttendances.stream().filter(f -> f.getAttendanceStatus().equals(AttendanceStatusEnum.REGISTERED.getValue())).collect(Collectors.toList());
        List<StudentAttendance> leaveData = studentAttendances.stream().filter(f -> f.getAttendanceStatus().equals(AttendanceStatusEnum.LEAVE.getValue())).collect(Collectors.toList());
        List<StudentAttendance> absentData = studentAttendances.stream().filter(f -> f.getAttendanceStatus().equals(AttendanceStatusEnum.ABSENCE.getValue())).collect(Collectors.toList());

        this.optClassSchedule(dto, scheduleDetail, signData, leaveData, absentData);


        //记录班级学员课表考勤数据
        this.optClassStudentSchedule(studentScheduleDetailMap, signData, leaveData, absentData);

        //记录班级学员考勤数据
        this.optClassStuStatistic(scheduleDetail, classStudents, classStudentIds);


        //更改课表数量 累加所有拼了这个课表的班级的统计数量
        //根据课表详情id获取班级课表
        this.optScheduleDetail(scheduleDetail);
    }

    private void optScheduleDetail(CourseScheduleDetail scheduleDetail) {
        List<SpellingClassPeriod> periods = this.spellingClassPeriodService.periods(Collections.singletonList(scheduleDetail.getId()));
        int totalActualNum = periods.stream().mapToInt(SpellingClassPeriod::getActualNum).reduce(0, Integer::sum);
        int totalAbsentNum = periods.stream().mapToInt(SpellingClassPeriod::getAbsentNum).reduce(0, Integer::sum);
        int totalLeaveNum = periods.stream().mapToInt(SpellingClassPeriod::getLeaveNum).reduce(0, Integer::sum);
        scheduleDetail.setActualNum(totalActualNum);
        scheduleDetail.setAbsentNum(totalAbsentNum);
        scheduleDetail.setLeaveNum(totalLeaveNum);
        scheduleDetail.setStatus(ScheduleDetailStatusEnum.ATTENDANCE_ING.getValue());
        this.scheduleDetailMapper.updateById(scheduleDetail);
    }

    private void optClassStuStatistic(CourseScheduleDetail scheduleDetail, List<ClassStudent> classStudents, List<String> classStudentIds) {
        List<ClassStudentScheduleDetail> newStuScheduleDetailData = this.classStudentScheduleDetailService.listStuScheduleDetail(classStudentIds, scheduleDetail.getId());
        Map<String, Integer> classStuSignNums = newStuScheduleDetailData.stream()
                .collect(Collectors.groupingBy(ClassStudentScheduleDetail::getClassStudentId, Collectors.summingInt(ClassStudentScheduleDetail::getSignNum)));

        Map<String, Integer> classStuAbsentNums = newStuScheduleDetailData.stream()
                .collect(Collectors.groupingBy(ClassStudentScheduleDetail::getClassStudentId, Collectors.summingInt(ClassStudentScheduleDetail::getAbsentNum)));

        Map<String, Integer> classStuLeaveNums = newStuScheduleDetailData.stream()
                .collect(Collectors.groupingBy(ClassStudentScheduleDetail::getClassStudentId, Collectors.summingInt(ClassStudentScheduleDetail::getLeaveNum)));

        classStudents.forEach(i -> {
            i.setSignNum(ParamUtils.isNotEmpty(classStuSignNums.get(i.getId())) ? classStuSignNums.get(i.getId()) : 0);
            i.setAbsentNum(ParamUtils.isNotEmpty(classStuAbsentNums.get(i.getId())) ? classStuAbsentNums.get(i.getId()) : 0);
            i.setLeaveNum(ParamUtils.isNotEmpty(classStuLeaveNums.get(i.getId())) ? classStuLeaveNums.get(i.getId()) : 0);
        });

        this.classStudentService.updateBatchById(classStudents);
    }

    private void optClassStudentSchedule(Map<String, ClassStudentScheduleDetail> studentScheduleDetailMap, List<StudentAttendance> signData, List<StudentAttendance> leaveData, List<StudentAttendance> absentData) {
        if (ParamUtils.isNotEmpty(signData)) {
            Long num;
            Map<String, Long> signMap = signData.stream().collect(Collectors.groupingBy(StudentAttendance::getClassStudentId, Collectors.counting()));
            for (String key : studentScheduleDetailMap.keySet()) {
                ClassStudentScheduleDetail classStudentScheduleDetail = studentScheduleDetailMap.get(key);
                num = signMap.get(classStudentScheduleDetail.getClassStudentId());
                classStudentScheduleDetail.setSignNum(ParamUtils.isNotEmpty(num) ? num.intValue() : 0);
            }
        } else {
            studentScheduleDetailMap.forEach((i, v) -> v.setSignNum(0));
        }

        if (ParamUtils.isNotEmpty(leaveData)) {
            Long num;
            Map<String, Long> leaveMap = leaveData.stream().collect(Collectors.groupingBy(StudentAttendance::getClassStudentId, Collectors.counting()));
            for (String key : studentScheduleDetailMap.keySet()) {
                ClassStudentScheduleDetail classStudentScheduleDetail = studentScheduleDetailMap.get(key);
                num = leaveMap.get(classStudentScheduleDetail.getClassStudentId());
                classStudentScheduleDetail.setLeaveNum(ParamUtils.isNotEmpty(num) ? num.intValue() : 0);
            }
        } else {
            studentScheduleDetailMap.forEach((i, v) -> v.setLeaveNum(0));
        }

        if (ParamUtils.isNotEmpty(absentData)) {
            Long num;
            Map<String, Long> absentMap = absentData.stream().collect(Collectors.groupingBy(StudentAttendance::getClassStudentId, Collectors.counting()));
            for (String key : studentScheduleDetailMap.keySet()) {
                ClassStudentScheduleDetail classStudentScheduleDetail = studentScheduleDetailMap.get(key);
                num = absentMap.get(classStudentScheduleDetail.getClassStudentId());
                classStudentScheduleDetail.setAbsentNum(ParamUtils.isNotEmpty(num) ? num.intValue() : 0);
            }
        } else {
            studentScheduleDetailMap.forEach((i, v) -> v.setAbsentNum(0));
        }

        studentScheduleDetailMap.forEach((k, v) -> this.classStudentScheduleDetailService.updateById(v));
    }

    private void optClassSchedule(AttendanceDTO dto, CourseScheduleDetail scheduleDetail, List<StudentAttendance> signData, List<StudentAttendance> leaveData, List<StudentAttendance> absentData) {
        //签到数
        int actualNum = signData.size();
        //请假数
        int leaveNum = leaveData.size();
        //未到数
        int absentNum = absentData.size();

        //更改班级课表
        SpellingClassPeriod spellingClassPeriod = this.spellingClassPeriodService.period(dto.getClassId(), scheduleDetail.getId());
        spellingClassPeriod.setActualNum(actualNum);
        spellingClassPeriod.setAbsentNum(absentNum);
        spellingClassPeriod.setLeaveNum(leaveNum);
        spellingClassPeriod.setClassAttendanceStatus(ScheduleDetailStatusEnum.ATTENDANCE_ING.getValue());
        this.spellingClassPeriodService.updateById(spellingClassPeriod);
    }

    private List<StudentAttendance> optStudentAttendance(AttendanceDTO dto, Date date, CourseScheduleDetail scheduleDetail, String attendanceStatus) {
        dto.getAttendances().forEach(i -> {
            StudentAttendance attendance = new StudentAttendance();
            attendance.setId(i.getId());
            attendance.setAttendanceStatus(attendanceStatus);
            attendance.setAttendanceTime(date);
            this.studentAttendanceMapper.updateById(attendance);
        });
        //获取更改后的考勤
        return this.studentAttendanceMapper.selectList(new LambdaQueryWrapper<StudentAttendance>()
                .eq(StudentAttendance::getScheduleDetailId, scheduleDetail.getId()));
    }


    /**
     * 校验是否到上课时间
     *
     * @param id 时段id
     */
    private CourseScheduleDetail checkClassTime(String id, Date date) {
        Date currentDate = TimeUtils.toDate(TimeUtils.toText(date, TimeUtils.MINUTE_FORMAT), TimeUtils.MINUTE_FORMAT);
        CourseScheduleDetail scheduleDetail = this.scheduleDetailMapper.selectById(id);
        String scheduleDateStr = TimeUtils.toText(scheduleDetail.getCourseDate(), TimeUtils.DATE_FORMAT) + " " + TimeUtils.toText(scheduleDetail.getStartTime(), DateUtils.HH_MM);
        Date scheduleDate = TimeUtils.toDate(scheduleDateStr, TimeUtils.MINUTE_FORMAT);
        if (!currentDate.after(scheduleDate)) {
            throw new BusinessException("上课时间未到不可进行考勤");
        }
        return scheduleDetail;
    }


    /**
     * 校验班级状态
     *
     * @param classId 班级id
     */
    private ClassInfo checkClassInfo(String classId) {
        ClassInfo classInfo = this.classInfoMapper.selectById(classId);
        if (!classInfo.getClassStatus().equals(ClassInfoStatusEnum.FINISH_CLASS.getValue()) && !classInfo.getClassStatus().equals(ClassInfoStatusEnum.OPEN_CLASS.getValue())) {
            throw new BusinessException("当前状态下不可进行考勤");
        }
        return classInfo;
    }
}
